import * as Box2D from "box2d.ts";
export class myBox2D {
  canvas: HTMLCanvasElement;
  ctx: CanvasRenderingContext2D;
  world: Box2D.b2World | null = null;

  animateFrame: number = 0;

  floorRect: Rect | null = null;
  rectangularBody: Rect | null = null;
  circular: Circular | null = null;
  polygon: Polygon | null = null;
  complex: Complex | null = null;
  revoluteJoint:RevoluteJoint|null = null
  test: number = 0;

  constructor(option: { canvas: HTMLCanvasElement; ctx: CanvasRenderingContext2D }) {
    this.canvas = option.canvas;
    this.ctx = option.ctx;
  }

  init() {
    this.world = new Box2D.b2World(new Box2D.b2Vec2(0, 9.8));
    this.world.m_allowSleep = true;

    this.floorRect = new Rect({
      position: { x: 0, y: 500 },
      density: 1.0,
      friction: 0.5,
      restitution: 0.2,
      size: {
        x: 640,
        y: 20,
      },
      world: this.world,
      userdata: { name: "floor" },
      type: Box2D.b2BodyType.b2_staticBody,
    });

    this.rectangularBody = new Rect({
      position: { x: 40, y: 100 },
      density: 1.0,
      friction: 0.5,
      restitution: 0.3,
      size: {
        x: 30,
        y: 50,
      },
      world: this.world,
      userdata: { name: "rectangularBody" },
      type: Box2D.b2BodyType.b2_dynamicBody,
    });

    this.circular = new Circular({
      position: { x: 130, y: 100 },
      density: 1.0,
      friction: 0.5,
      restitution: 0.7,
      radius: 30,
      world: this.world,
      userdata: { name: "circular" },
      type: Box2D.b2BodyType.b2_dynamicBody,
    });

    this.polygon = new Polygon({
      position: { x: 230, y: 50 },
      density: 1.0,
      friction: 0.5,
      restitution: 0.2,
      points: [new Box2D.b2Vec2(0, 0), new Box2D.b2Vec2(40, 50), new Box2D.b2Vec2(50, 100), new Box2D.b2Vec2(-50, 100), new Box2D.b2Vec2(-40, 50)],
      world: this.world,
      userdata: { name: "polygon" },
      type: Box2D.b2BodyType.b2_dynamicBody,
    });

    this.complex = new Complex({
      position: { x: 350, y: 50 },
      density: 1.0,
      friction: 0.5,
      restitution: 0.2,
      shapes: [new Box2D.b2CircleShape(40), new Box2D.b2PolygonShape().SetAsArray([new Box2D.b2Vec2(0, 0), new Box2D.b2Vec2(40, 50), new Box2D.b2Vec2(50, 100), new Box2D.b2Vec2(-50, 100), new Box2D.b2Vec2(-40, 50)])],
      world: this.world,
      userdata: { name: "complex" },
      type: Box2D.b2BodyType.b2_dynamicBody,
    });

    this.revoluteJoint = new RevoluteJoint({
      body1: new Rect({
        position: { x: 480, y: 50 },
        density: 1.0,
        friction: 0.5,
        restitution: 0.5,
        size: {
          x: 50,
          y: 10,
        },
        world: this.world,
        userdata: { name: "rectangularBody" },
        type: Box2D.b2BodyType.b2_dynamicBody,
      }),
      body2: new Polygon({
        position: { x: 470, y: 50 },
        density: 1.0,
        friction: 0.5,
        restitution: 0.5,
        points: [
          new Box2D.b2Vec2(0, 0),
          new Box2D.b2Vec2(40, 50),
          new Box2D.b2Vec2(50, 100),
          new Box2D.b2Vec2(-50, 100),
          new Box2D.b2Vec2(-40, 50),
        ],
        world: this.world,
        userdata: { name: "polygon" },
        type: Box2D.b2BodyType.b2_dynamicBody,
      }),
      world:this.world,
      jointCenter:new Box2D.b2Vec2(470,50)
    });

    
    

    let node = this.world?.GetBodyList();
    while (node) {
      let b = node;
      node = node.GetNext();
      let userdata = b.GetUserData();
      
      if (userdata.name == "floor") {
        let fixture = b.GetFixtureList();
        console.log(fixture)
        this.floorRect?.render(this.ctx, b.GetPosition(), b.GetAngle(),fixture!);
      }
      if (userdata.name == "rectangularBody") {
        let fixture = b.GetFixtureList();
        console.log(fixture)
        this.rectangularBody?.render(this.ctx, b.GetPosition(), b.GetAngle(),fixture!);
      }
    }
    this.animate();
  }

  animate = (t?: number) => {
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    this.animateFrame = requestAnimationFrame(this.animate);
    //刷新帧率
    this.world?.Step(1 / 60, 8, 3);

    let node = this.world?.GetBodyList();
    while (node) {
      let b = node;
      node = node.GetNext();
      let userdata = b.GetUserData();
      let fixture = b.GetFixtureList();
      if (userdata.name == "floor") {
        this.floorRect?.render(this.ctx, b.GetPosition(), b.GetAngle(),fixture!);
      }
      if (userdata.name == "rectangularBody") {
        this.rectangularBody?.render(this.ctx, b.GetPosition(), b.GetAngle(),fixture!);
      }
      if (userdata.name == "circular") {
        this.circular?.render(this.ctx, b.GetPosition(), b.GetAngle());
      }
      if (userdata.name == "polygon") {
        this.polygon?.render(this.ctx, b.GetPosition(), b.GetAngle());
      }
      if (userdata.name == "complex") {
        while (fixture) {
          let f = fixture;
          fixture = fixture.GetNext();
          this.complex?.render(this.ctx, b.GetPosition(), b.GetAngle(), f);
        }
      }
    }

    // this.test++;
    // if (this.test > 100) {
    //   cancelAnimationFrame(this.animateFrame);
    // }
  };
}

class RevoluteJoint {
  body1: Rect | Circular | Polygon | Complex;
  body2: Rect | Circular | Polygon | Complex;
  jointDef: Box2D.b2RevoluteJointDef;
  jointCenter: Box2D.b2Vec2;
  constructor(option: { body1: Rect | Circular | Polygon | Complex; body2: Rect | Circular | Polygon | Complex; world: Box2D.b2World; jointCenter: Box2D.b2Vec2 }) {
    this.body1 = option.body1;
    this.body2 = option.body2;

    this.jointDef = new Box2D.b2RevoluteJointDef();
    this.jointCenter = option.jointCenter;
    this.jointDef.Initialize(this.body1.body, this.body2.body, this.jointCenter);
    option.world.CreateJoint(this.jointDef);
  }
}

class Complex {
  body: Box2D.b2Body;
  bodyDef: Box2D.b2BodyDef;
  fixtrueDef: Box2D.b2FixtureDef;

  constructor(option: { position: { x: number; y: number }; density: number; friction: number; restitution: number; shapes: any[]; world: Box2D.b2World; userdata: any; type: Box2D.b2BodyType }) {
    this.bodyDef = new Box2D.b2BodyDef();
    this.bodyDef.type = option.type;
    this.bodyDef.position.x = option.position.x;
    this.bodyDef.position.y = option.position.y;
    this.body = option.world.CreateBody(this.bodyDef);
    this.body.SetUserData(option.userdata);

    this.fixtrueDef = new Box2D.b2FixtureDef();
    this.fixtrueDef.density = option.density;
    this.fixtrueDef.friction = option.friction;
    this.fixtrueDef.restitution = option.restitution;
    option.shapes.forEach((item) => {
      this.fixtrueDef.shape = item;
      this.body.CreateFixture(this.fixtrueDef);
    });
  }

  render(ctx: CanvasRenderingContext2D, position: Box2D.b2Vec2, angle: number, f: Box2D.b2Fixture) {
    ctx.save();
    ctx.translate(position.x, position.y);
    ctx.rotate(angle);
    ctx.beginPath();
    let fixtrueDef = f.GetShape();
    if (fixtrueDef.m_radius) {
      ctx.arc(0, 0, fixtrueDef.m_radius, 0, 2 * Math.PI);
    }
    if (fixtrueDef.m_vertices) {
      let pointList: { x: number; y: number }[] = fixtrueDef.m_vertices;
      pointList.forEach((item, index) => {
        if (index == 0) {
          ctx.moveTo(item.x, item.y);
        }
        ctx.lineTo(item.x, item.y);
      });
    }
    ctx.closePath();
    ctx.stroke();
    ctx.restore();
  }
}

class Polygon {
  body: Box2D.b2Body;
  bodyDef: Box2D.b2BodyDef;
  fixtrueDef: Box2D.b2FixtureDef;

  constructor(option: { position: { x: number; y: number }; density: number; friction: number; restitution: number; points: Box2D.b2Vec2[]; world: Box2D.b2World; userdata: any; type: Box2D.b2BodyType }) {
    this.bodyDef = new Box2D.b2BodyDef();
    this.bodyDef.type = option.type;
    this.bodyDef.position.x = option.position.x;
    this.bodyDef.position.y = option.position.y;
    this.fixtrueDef = new Box2D.b2FixtureDef();
    this.fixtrueDef.density = option.density;
    this.fixtrueDef.friction = option.friction;
    this.fixtrueDef.restitution = option.restitution;
    this.fixtrueDef.shape = new Box2D.b2PolygonShape().SetAsArray(option.points);
    this.body = option.world.CreateBody(this.bodyDef);
    this.body.SetUserData(option.userdata);
    this.body.CreateFixture(this.fixtrueDef);
  }

  render(ctx: CanvasRenderingContext2D, position: Box2D.b2Vec2, angle: number) {
    ctx.save();
    ctx.translate(position.x, position.y);
    ctx.rotate(angle);
    ctx.beginPath();
    let pointList: { x: number; y: number }[] = this.fixtrueDef.shape.m_vertices;
    pointList.forEach((item, index) => {
      if (index == 0) {
        ctx.moveTo(item.x, item.y);
      }
      ctx.lineTo(item.x, item.y);
    });
    ctx.closePath();
    ctx.stroke();
    ctx.restore();
  }
}

class Circular {
  body: Box2D.b2Body;
  bodyDef: Box2D.b2BodyDef;
  fixtrueDef: Box2D.b2FixtureDef;

  constructor(option: { position: { x: number; y: number }; density: number; friction: number; restitution: number; radius: number; world: Box2D.b2World; userdata: any; type: Box2D.b2BodyType }) {
    this.bodyDef = new Box2D.b2BodyDef();
    this.bodyDef.type = option.type;
    this.bodyDef.position.x = option.position.x;
    this.bodyDef.position.y = option.position.y;
    this.fixtrueDef = new Box2D.b2FixtureDef();
    this.fixtrueDef.density = option.density;
    this.fixtrueDef.friction = option.friction;
    this.fixtrueDef.restitution = option.restitution;
    this.fixtrueDef.shape = new Box2D.b2CircleShape(option.radius);

    this.body = option.world.CreateBody(this.bodyDef);
    this.body.SetUserData(option.userdata);
    this.body.CreateFixture(this.fixtrueDef);
  }

  render(ctx: CanvasRenderingContext2D, position: Box2D.b2Vec2, angle: number) {
    ctx.save();
    ctx.translate(position.x, position.y);
    ctx.rotate(angle);
    ctx.beginPath();
    ctx.arc(0, 0, this.fixtrueDef.shape.m_radius, 0, 2 * Math.PI);
    ctx.closePath();
    ctx.stroke();
    ctx.restore();
  }
}

class Rect {
  body: Box2D.b2Body;
  bodyDef: Box2D.b2BodyDef;
  fixtrueDef: Box2D.b2FixtureDef;

  constructor(option: { position: { x: number; y: number }; density: number; friction: number; restitution: number; size: { x: number; y: number }; world: Box2D.b2World; userdata: any; type: Box2D.b2BodyType }) {
    this.bodyDef = new Box2D.b2BodyDef();
    this.bodyDef.type = option.type;
    this.bodyDef.position.x = option.position.x;
    this.bodyDef.position.y = option.position.y;
    this.fixtrueDef = new Box2D.b2FixtureDef();
    this.fixtrueDef.density = option.density;
    this.fixtrueDef.friction = option.friction;
    this.fixtrueDef.restitution = option.restitution;
    this.fixtrueDef.shape = new Box2D.b2PolygonShape().SetAsBox(option.size.x, option.size.y);

    this.body = option.world.CreateBody(this.bodyDef);
    this.body.SetUserData(option.userdata);
    this.body.CreateFixture(this.fixtrueDef);
  }

  render(ctx: CanvasRenderingContext2D, position: Box2D.b2Vec2, angle: number,fixture:Box2D.b2Fixture) {
    ctx.save();
    ctx.translate(position.x, position.y);
    ctx.rotate(angle);
    ctx.beginPath();
    let pointList: { x: number; y: number }[] = fixture.m_shape.m_vertices;
    pointList.forEach((item, index) => {
      if (index == 0) {
        ctx.moveTo(item.x, item.y);
      }
      ctx.lineTo(item.x, item.y);
    });
    ctx.closePath();
    ctx.stroke();
    ctx.restore();
  }
}
